Skip to content

strings: add Builder.write_u_decimal and write_decimal JS-backend parity - #27522

Merged
medvednikov merged 2 commits into
vlang:masterfrom
enghitalo:strings-builder-write-u-decimal
Jun 26, 2026
Merged

strings: add Builder.write_u_decimal and write_decimal JS-backend parity#27522
medvednikov merged 2 commits into
vlang:masterfrom
enghitalo:strings-builder-write-u-decimal

Conversation

@enghitalo

Copy link
Copy Markdown
Contributor

Builder.write_decimal(i64) (the zero-alloc decimal writer added in #19625) had two
parity gaps:

  1. No unsigned u64 variant. The API is i64-typed, so values above max_i64
    could not be written allocation-free — callers had to fall back to
    write_string(n.str()), which allocates.
  2. The JS backend had no write_decimal at all. It lived only in builder.c.v, so
    code relying on it did not compile on the JS backend.

This PR:

  • adds pub fn (mut b Builder) write_u_decimal(n u64) to builder.c.v — same stack-buffer
    approach as write_decimal, no sign branch, covering the entire u64 range;
  • mirrors write_decimal and write_u_decimal into builder.js.v (using write_u8,
    since the JS backend has no write_ptr);
  • adds test_write_u_decimal covering 0, max_i64, max_i64 + 1, and max_u64.

Tests

  • v test vlib/strings/builder_test.v → OK
  • v -silent test vlib/strings/ → 9 passed, 9 total
  • Verified on the JS backend (v -b js run): write_u_decimal(max_u64)
    18446744073709551615, and normal i64/u64 values match the C backend.

Note: on the JS backend the min_i64 constant already evaluates incorrectly (plain
println(min_i64) / min_i64.str() print -1) — a pre-existing JS-backend i64
limitation, unrelated to this change. All other values format correctly on both backends.

Fixes #27510

Builder.write_decimal(i64) was added in vlang#19625, but there was no unsigned
variant (values above max_i64 had to go through n.str(), which allocates),
and the JS backend Builder had no write_decimal at all, so code using it was
not portable across backends.

- add write_u_decimal(n u64) to builder.c.v (zero-alloc, full u64 range)
- mirror write_decimal and write_u_decimal into builder.js.v
- add test_write_u_decimal covering 0, max_i64, max_i64 + 1 and max_u64

Fixes vlang#27510

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 811733e9d8

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread vlib/strings/builder.js.v Outdated
b.write_u8(0x30)
return
}
if n == min_i64 {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Handle runtime min_i64 on the JS backend

On the JS backend a true -9223372036854775808 value can still be produced at runtime, for example with '-9223372036854775808'.i64(), but the backend's min_i64 constant is known to lower incorrectly there, so this guard is skipped. The following digit loop then negates/formats the signed 64-bit minimum through the normal path, which is exactly the overflow case the C implementation special-cases, so write_decimal is not actually parity-correct for that input.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@enghitalo ☝🏼

…4 constant

Address the review on vlang#27522: on the JS backend the min_i64 constant lowers
incorrectly, so the 'if n == min_i64' guard was skipped and a genuine runtime
min_i64 (e.g. from '-9223372036854775808'.i64()) fell into the digit loop where
negating it overflows i64.

Compute the magnitude with wrapping unsigned arithmetic (u64(0) - u64(n)) and
delegate to write_u_decimal instead. This is parity-correct for runtime min_i64
on both backends, removes the dependence on the constant, and drops the
allocating n.str() fallback so the C path is now allocation-free for every input.
@medvednikov
medvednikov merged commit 8a79300 into vlang:master Jun 26, 2026
77 of 90 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

strings.Builder.write_decimal: add an unsigned u64 variant and JS-backend parity

3 participants